package com.googlecode.bizyap.impl.tomcat.valve;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Set;

import javax.security.auth.Subject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.catalina.Context;
import org.apache.catalina.Realm;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;

import com.googlecode.bizyap.core.AgentService;
import com.googlecode.bizyap.core.Application;
import com.googlecode.bizyap.core.Authenticator;
import com.googlecode.bizyap.core.SSOContext;
import com.googlecode.bizyap.core.UserPrincipal;
import com.googlecode.bizyap.core.logging.Logger;
import com.googlecode.bizyap.core.logging.LoggerFactory;
import com.googlecode.bizyap.core.tools.CookieManager;
import com.googlecode.bizyap.core.tools.XMLHttpRequestTool;
import com.googlecode.bizyap.impl.tomcat.Globals;

/**
 *
 * @author mehmet.dogan
 *
 */
public class SSOValve extends ValveBase implements Valve {

	protected final static Logger log = LoggerFactory.getLogger(SSOValve.class);

	public void invoke(Request request, Response response) throws IOException, ServletException {
		Application app = AgentService.get().getApplication(request.getContextPath());
		try {
			SSOContext.setApplication(app);

			if(!app.isValid()){
				log.error("This application is not valid: [" + app.getContextPath() + "]. Please check SSO configuration file!");
				sendError(request, response);
				return;
			}

			String url = request.getServletPath();
			if(app.requiresSecurityCheck(url)){

				Context context = request.getContext();
				ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
				try {
					if(context.getLoader() != null) {
						ClassLoader webContextClassLoader = context.getLoader().getClassLoader();
						Thread.currentThread().setContextClassLoader(webContextClassLoader);
			        }

					HttpSession session = request.getSession();
					Subject subject = (Subject) session.getAttribute(Globals.SUBJECT_ATTR);

					if((app.isLoggingOut(url)) ||
						(subject!=null && CookieManager.didUserLogOut(request, response))){

						if(log.isDebugEnabled())log.debug("USER IS LOGGING OUT...");
						session.invalidate();
						CookieManager.userLoggedOut(request, response);
						sendRedirectLogout(request, response);
						return;
					}

					if(subject==null) {
						String[] tokens = request.getParameterValues(Globals.TOKEN_PARAM);
						if(tokens==null || tokens.length == 0) {
							if(log.isDebugEnabled())log.debug("USER NEITHER LOGGED IN NOR HAS A TOKEN PARAM... ");
							sendRedirectLogin(request, response);
							return;
						}
						else {
							// just care about last token param...
							String token = tokens[tokens.length-1];
							if(token==null || "".equals(token)) {
								if(log.isDebugEnabled())log.debug("USER NEITHER LOGGED IN NOR HAS A TOKEN PARAM... ");
								sendRedirectLogin(request, response);
								return;
							}
								
							if(log.isDebugEnabled())log.debug("USER NOT LOGGED IN BUT ALSO HAS A TOKEN PARAM...");
							String username = Authenticator.authenticate(token);
							if(log.isDebugEnabled())log.debug("USERNAME: " + username);
							if(username==null || "".equals(username) || "null".equals(username)){
								sendRedirectLogin(request, response);
								return;
							}

							Realm realm = request.getContext().getRealm();
							Principal user = realm.authenticate(username, "");
							UserPrincipal userPrincipal = (UserPrincipal)user;
							setUserPrincipal(request, userPrincipal);
							CookieManager.userLoggedIn(request, response);
						}
					}
					else {
						if(log.isDebugEnabled())log.debug("USER ALREADY LOGGED IN....");
						Iterator<UserPrincipal> user = subject.getPrincipals(UserPrincipal.class).iterator();
						if(user.hasNext()){
							request.setUserPrincipal(user.next());
						}
						if(log.isDebugEnabled())log.debug("USER PRINCIPAL: " + request.getUserPrincipal());
					}

				}
				finally {
					Thread.currentThread().setContextClassLoader(threadContextClassLoader);
				}
			}
			getNext().invoke(request, response);
		}
		finally {
			SSOContext.removeApplication();
		}
	}

	protected void sendError(HttpServletRequest request, HttpServletResponse response) throws IOException{
		PrintWriter out = response.getWriter();
		out.append("This application could not be deployed correctly! Please check SSO configuration file...");
		out.flush();
	}

	protected void sendRedirectLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
		if(XMLHttpRequestTool.isXMLHttpRequest(request)){
			XMLHttpRequestTool.showLogin(response);
		}
		else {
			response.sendRedirect(returnLoginURL(request, response));
		}
	}

	protected void sendRedirectLogout(HttpServletRequest request, HttpServletResponse response) throws IOException {
		if(XMLHttpRequestTool.isXMLHttpRequest(request)){
			XMLHttpRequestTool.showLogin(response);
		}
		else {
			response.sendRedirect(returnLogoutURL(request, response));
		}
	}

	protected String returnLoginURL(HttpServletRequest request, HttpServletResponse response){
		String app = getRequestedApplicationURL(request, response);
		String url = AgentService.get().getSsoLoginUrl() + "?" + Globals.TARGET_URL_PARAM + "=" + app;
		return response.encodeRedirectURL(url);
	}

	protected String returnLogoutURL(HttpServletRequest request, HttpServletResponse response){
		String app = getAbsoluteApplicationURL(request, response);
		String url = AgentService.get().getSsoLogoutUrl() + "?" + Globals.TARGET_URL_PARAM + "=" + app;
		return response.encodeRedirectURL(url);
	}

	protected String getAbsoluteApplicationURL(HttpServletRequest request, HttpServletResponse response){
		StringBuffer url = request.getRequestURL();
		String urlStr = url.substring(0, url.lastIndexOf("/") + 1) + "?sso=true";
		try {
			urlStr = URLEncoder.encode(urlStr, "UTF-8");
		}
		catch (Exception e) {
			log.error("", e);
		}
		return urlStr;
	}

	protected String getRequestedApplicationURL(HttpServletRequest request, HttpServletResponse response){
		StringBuffer url = request.getRequestURL().append("?sso=true");
		if(log.isDebugEnabled())log.debug("URL-pre: " + url.toString());
		
		Enumeration names = request.getParameterNames();
		while(names.hasMoreElements()) {
			String param = (String) names.nextElement();
			// ignore token and sso params...
			if(Globals.TOKEN_PARAM.equals(param) || "sso".equals(param)) {
				continue;
			}
			
			String value = request.getParameter(param);
			url.append("&").append(param).append("=").append(value);
		}
		
		String urlStr = url.toString();
		if(log.isDebugEnabled())log.debug("URL-post: " + urlStr);
		
		try {
			urlStr = URLEncoder.encode(urlStr, "UTF-8");
		}
		catch (Exception e) {
			log.error("", e);
		}
		return urlStr;
	}

	protected void setUserPrincipal(Request request, UserPrincipal user){
		HttpSession session = request.getSession();
		request.setUserPrincipal(user);
		
		Subject subject = (Subject) session.getAttribute(Globals.SUBJECT_ATTR);
		if(subject==null) {
			log.info("SUBJECT IS NULL... USER: " + user.getName());
			subject = new Subject();
			session.setAttribute(Globals.SUBJECT_ATTR, subject);
			subject.getPrincipals().add(user);
		}

		Set<Principal> roles = subject.getPrincipals();
		roles.addAll(user.getAppRoles());

		session.setAttribute(Globals.USER_ATTR, user.getAppUser());
		session.setAttribute(Globals.ROLES_ATTR, user.getAppRoles());
	}
}
